#ifndef GTL_CONCURRENCY_HEADER_GUARD
#define GTL_CONCURRENCY_HEADER_GUARD

#include "gtlCPPUtils.h"
#include "gtlConcurrency_Atomic.h"
#include "gtlConcurrency_Threads.h"

namespace GTL
{
  namespace Concurrency
  {
    /// T should be lightweight, preferably without an important constructor/destructor
    template <typename T, unsigned int BUFFER_SIZE = 1>
    class Channel
    {
    public:
      Channel() : mImplementation(new ChannelImplementation<T, BUFFER_SIZE>) { }
      Channel(const Channel<T>& other) : mImplementation(other.mImplementation) {}
      Channel& operator=(const Channel<T>& other) { mImplementation = other.mImplementation; }
      virtual ~Channel() { mImplementation = 0; }

      void Send(const T& object)
      {
				ThreadID myID = GetCurrentThreadID();

        do
        {
          mImplementation->Release(myID); /// let go if we have lock
          do Yield(); while (!mImplementation->Hold(myID));  /// wait until we can get lock

        } while (!IsReadyForWriting()); /// if it turns out the buffer is full, repeat above, essentially waiting for another thread to empty the buffer

        mImplementation->mBuffer[mImplementation->mBufferTaken] = object;
        mImplementation->mBufferTaken++;

        mImplementation->Release(myID);
      }
      
      T Recv()
      {
				ThreadID myID = GetCurrentThreadID();

        do
        {
          mImplementation->Release(myID); /// let go if we have lock
          do Yield(); while (!mImplementation->Hold(myID));  /// wait until we can get lock

        } while (!IsReadyForReading()); /// if it turns out the buffer is empty, repeat above, essentially waiting for another thread to fill the buffer

        T result = mImplementation->mBuffer[mImplementation->mBufferTaken];
        mImplementation->mBufferTaken--;

        mImplementation->Release(myID);

        return result;
      }

      void Recv(T& obj)
      {
				ThreadID myID = GetCurrentThreadID();
        do
        {
          mImplementation->Release(myID); /// let go if we have lock
          do Yield(); while (!mImplementation->Hold(myID));  /// wait until we can get lock

        } while (!IsReadyForReading()); /// if it turns out the buffer is empty, repeat above, essentially waiting for another thread to fill the buffer

        mImplementation->mBufferTaken--;
        obj = mImplementation->mBuffer[mImplementation->mBufferTaken];

        mImplementation->Release(myID);
      }

      Channel& operator<<(const T& object) { Send(object); return *this; }
      Channel& operator>>(T& object) { Recv(object); return *this; }

      bool IsReadyForWriting() const { return mImplementation->mBufferTaken < BUFFER_SIZE; }
      bool IsReadyForReading() const { return mImplementation->mBufferTaken > 0; }
      unsigned int GetWaitingCount() const { return mImplementation->mBufferTop; }
    protected:
      template <typename TT, unsigned int BUFFER_SIZET>
      struct ChannelImplementation
      {
        ThreadID mCurrentHolder;
        TT mBuffer[BUFFER_SIZET];
        unsigned int mBufferTaken;
        ChannelImplementation() : mCurrentHolder(0), mBufferTaken(0) { }

        bool Hold(ThreadID holder)
        {
          //GTL_ATOMIC_COMPARE_AND_EXCHANGE_POINTER((void**)&mCurrentHolder, 0, (void*)holder);
          GTL_ATOMIC_COMPARE_AND_EXCHANGE(&mCurrentHolder, 0, holder);
          return mCurrentHolder == holder;
        }

				bool Release(ThreadID holder)
				{
					GTL_ATOMIC_COMPARE_AND_EXCHANGE(&mCurrentHolder, holder, 0);
					return mCurrentHolder == 0;
				}
      };
      Utilities::ReferenceCountedSmartPointer<ChannelImplementation<T, BUFFER_SIZE> > mImplementation;
    };
  }
}

#endif